package dom.xpath.query.xpath.expr;

import dom.xpath.query.EvalException;
import dom.xpath.query.xpath.XPContext;
import dom.xpath.query.xpath.values.Item;
import dom.xpath.query.xpath.values.NodeSet;
import dom.xpath.util.Array;

/**
 * FTUnion Expression. This expresses the union of two FTContains results.
 * 
 * @author Workgroup DBIS, University of Konstanz 2005-07, ISC License
 * @author Sebastian Gath
 */
public final class FTUnion extends ArrayExpr {
  /**
   * Constructor.
   * @param e operands joined with the union operator
   */
  public FTUnion(final Expression[] e) {
    exprs = e;
  }

  @Override
  public NodeSet eval(final XPContext ctx) throws EvalException {
    int[][] res = null;
    int[][] tmp;
      
    Item it = exprs[0].eval(ctx);
    if (it instanceof NodeSet) {
      res = ((NodeSet) it).ftidpos;
   
      for (int i = 1; i < exprs.length; i++) {
        it = exprs[i].eval(ctx);
        if (it instanceof NodeSet) {
          tmp = ((NodeSet) it).ftidpos;
          res = calcualteFtOr(res, tmp);
        }
      }
      return new NodeSet(Array.extractIDsFromData(res), ctx, res);
    }
    return null;
  }

  @Override
  public Expression compile(final XPContext ctx) throws EvalException {
    final int el = exprs.length;
    for(int e = 0; e != el; e++) {
        exprs[e] = exprs[e].compile(ctx);
    }
    return this;
  }
  
  /**
   * Builds an or-conjunction of values1 and values2.
   * @param values1 input set
   * @param values2 input set
   * @return union set int[][]
   */
  public static int[][] calcualteFtOr(final int[][] values1,
      final int[][] values2) {
    
    int[][] val1 = values1;
    int[][] val2 = values2;
    
    if(val1 == null || val1[0].length == 0) {
      return val2;
    } else if(val2 == null || val2[0].length == 0) {
      return val1;
    }

    int[][] maxResult = new int[2][val1[0].length + val2[0].length];

    // calculate maximum
    int max = Math.max(val1[0].length, val2[0].length);
    if(max == val1.length) {
      int[][] tmp = val1;
      val1 = val2;
      val2 = tmp;
    }

    // run variable for values1
    int i = 0;
    // run variable for values2
    int k = 0;
    // count inserted elements
    int counter = 0;

    int cmpResult;
    // process smaller set
    while(val1[0].length > i) {
      if(k >= val2[0].length) {
        break;
      }
      cmpResult = Array.compareIntArrayEntry(val1[0][i], 
          val1[1][i], val2[0][k], val2[1][k]);
      if(cmpResult == 1 || cmpResult == 2) {
        // same Id, pos0 < pos1 oder id0 < id1
        maxResult[0][counter] = val2[0][k];
        maxResult[1][counter] = val2[1][k];
        counter++;
        k++;
      } else if(cmpResult == -1 || cmpResult == -2) {
        // same Id, pos0 > pos1 oder id0 > id1
        maxResult[0][counter] = val1[0][i];
        maxResult[1][counter] = val1[1][i];
        counter++;
        i++;
        //k++;
      } else {
        // ids and pos identical
        maxResult[0][counter] = val1[0][i];
        maxResult[1][counter] = val1[1][i];
        counter++;
        i++;
        k++;
      }
    }

    if(counter == 0) return null;

    int[][] returnArray;

    // all elements form values2 are processed
    if(k == val2[0].length && i < val1[0].length) {
      //returnArray = new int[counter+values1.length-i][2];
      returnArray = new int[2][counter + val1[0].length - i];
      // copy left values (bigger than last element values2) from values1
      System.arraycopy(val1[0], i, returnArray[0], counter, 
          val1[0].length - i);
      System.arraycopy(val1[1], i, returnArray[1], counter, 
          val1[0].length - i);
    } else {
      // all elements form values1 are processed
      returnArray = new int[2][counter + val2[0].length - k];
      // copy left values (bigger than last element values1) from values2
      System.arraycopy(val2[0], k, returnArray[0], counter, 
          val2[0].length - k);
      System.arraycopy(val2[1], k, returnArray[1], counter, 
          val2[0].length - k);
    }

    System.arraycopy(maxResult[0], 0, returnArray[0], 0, counter);
    System.arraycopy(maxResult[1], 0, returnArray[1], 0, counter);
    return returnArray;
  }

}
